﻿using System.Collections;
using System.Collections.Generic;
using ActionLogic;
using System;
using BehaviorModel.Block.ServiceChannel;
using ComputationalCore.Helper;
using Model;
using System.Linq;

namespace BehaviorModel
{
    public enum ChoosingDiscipline
    {
        Fifo,
        Lifo,
        Random
    }

    /// <summary>
    /// Describes request buffer implementation.
    /// </summary>
    public class RequestBuffer : IRequestBuffer
    {
        #region Fields

        private LinkedList<Request> _buffer;
        private event Action _onRequestAdded;

        #endregion

        #region Properties

        public int MaxCapacity { get; set; }

        public int MaxWaitingTime { get; set; }

        public int Count { get { return _buffer.Count; } }

        public ChoosingDiscipline Discipline { get; set; }

        public event Action OnRequestAdded
        {
            add
            {
                if (_onRequestAdded != null)
                {
                    if (_onRequestAdded.GetInvocationList().Any(handler => handler.Target == value.Target))
                    {
                        return;
                    }
                }
                _onRequestAdded += value;
            }
            remove { if (_onRequestAdded != null) _onRequestAdded -= value; }
        }

        public event Action<Request> OnRequestRejected;

        #endregion

        #region Constructor

        public RequestBuffer()
        {
            _buffer = new LinkedList<Request>();
            MaxCapacity = 0;
            MaxWaitingTime = 0;
            Discipline = ChoosingDiscipline.Fifo;
        }

        #endregion

        #region Methods

        public void Add(Request request)
        {
            if (!IsFullFilled())
            {
                _buffer.AddLast(request);
                RaiseRequestAdded();
                return;
            }

            RaiseRequestRejected(request);
        }

        private bool IsFullFilled()
        {
            return MaxCapacity != 0 && _buffer.Count >= MaxCapacity;
        }

        private void RaiseRequestRejected(Request request)
        {
            if (OnRequestRejected != null)
            {
                OnRequestRejected(request);
            }
        }

        private void RaiseRequestAdded()
        {
            if (_onRequestAdded != null)
            {
                _onRequestAdded();
            }
        }

        public Request Get()
        {
            switch (Discipline)
            {
                case ChoosingDiscipline.Fifo: return GetFifoRequest();
                case ChoosingDiscipline.Lifo: return GetLifoRequest();
                case ChoosingDiscipline.Random: return GetRandomRequest();
            }

            throw new InvalidOperationException("Буфер заявок пуст.");
        }

        private Request GetRandomRequest()
        {
            var request = _buffer.ElementAt(RandomHelper.NextInt(0, _buffer.Count));
            _buffer.Remove(request);
            return request;
        }

        private Request GetLifoRequest()
        {
            var request = _buffer.Last();
            _buffer.RemoveLast();
            return request;
        }

        private Request GetFifoRequest()
        {
            var request = _buffer.First();
            _buffer.RemoveFirst();
            return request;
        }

        public bool IsEmpty()
        {
            return _buffer.Count == 0;
        }

        public void Clear()
        {
            _buffer.Clear();
        }

        public void UpdateRequestsInQueue(double waitingTime)
        {
            IncreaseWaitingTimeInQueue(waitingTime);
            RejectOverwaitingRequests();
        }

        private void IncreaseWaitingTimeInQueue(double waitingTime)
        {
            foreach (var request in _buffer)
            {
                request.LastQueueWaitingTime += waitingTime;
                request.CommonWaitingTime += waitingTime;
            }
        }

        private void RejectOverwaitingRequests()
        {
            for (var i = _buffer.Count - 1; i >= 0; i--)
            {
                var request = _buffer.ElementAt(i);
                if (!IsWaitingTimeOver(request)) continue;
                RejectRequestFromQueue(request);
            }
        }

        private void RejectRequestFromQueue(Request request)
        {
            _buffer.Remove(request);
            RaiseRequestRejected(request);
        }

        public bool IsWaitingTimeOver(Request request)
        {
            return MaxWaitingTime != 0 && request.LastQueueWaitingTime >= MaxWaitingTime;
        }

        #endregion
    }
}